package com.aphrodite.ui.widgets.barcode;

import java.util.Hashtable;
import java.util.Vector;

import com.aphrodite.util.CharUtil;
import com.aphrodite.util.CharacterIterator;
import com.aphrodite.util.StringCharacterIterator;

/**
 * This is a concrete implementation of the Standard 2 of 5 barcode
 * 
 * The Standard 2 of 5 barcode requires an even number of characters. At the
 * same time, a modulo 10 check digit can be used. The default constructor
 * presumes that if the barcode is of an odd length, then a check digit is
 * required. This will automaticaly be generated. If the constructor with the
 * check digit flag is used, the check digit flag is flase and the length of the
 * barcode is odd, then an exception will be thrown.
 */
class Std2of5Barcode extends LinearBarcode {
	static class ModuleTemplate {
		private final static int CHECKSUM_WEIGHT_EVEN = 1;

		private final static int CHECKSUM_WEIGHT_ODD = 3;
		static final Module END_CHAR = new Module(new int[] { 3, 1, 1, 1, 3, 1 });

		protected static final Hashtable SET = new Hashtable();
		static final Module START_CHAR = new Module(new int[] { 3, 1, 3, 1, 1, 1 });

		static {
			init();
		}

		/**
		 * Returns the module that represents the specified character.
		 * 
		 * @param key
		 *            The data character to get the encoding module for
		 * @return The module that encodes the given char
		 */
		static Module getModule(String key) {
			/*
			 * Module module = null; module = (Module) SET.get(key);
			 * module.setSymbol(key); return module;
			 */
			Module module = null;
			int[] bar = null;
			bar = (int[]) SET.get(key);

			// this should always be true, but best to check just in case...
			if (bar != null && bar.length == 5) {

				// create the new module based on the selected bar val with the
				// space as 1
				module = new Module(new int[] { bar[0], 1, bar[1], 1, bar[2], 1, bar[3], 1, bar[4], 1 });

				module.setSymbol(key);
			}

			return module;

		}

		/**
		 * Initialise the module definitions.
		 */
		private static void init() {
			SET.put("0", new int[] { 1, 1, 3, 3, 1 });
			SET.put("1", new int[] { 3, 1, 1, 1, 3 });
			SET.put("2", new int[] { 1, 3, 1, 1, 3 });
			SET.put("3", new int[] { 3, 3, 1, 1, 1 });
			SET.put("4", new int[] { 1, 1, 3, 1, 3 });
			SET.put("5", new int[] { 3, 1, 3, 1, 1 });
			SET.put("6", new int[] { 1, 3, 3, 1, 1 });
			SET.put("7", new int[] { 1, 1, 1, 3, 3 });
			SET.put("8", new int[] { 3, 1, 1, 3, 1 });
			SET.put("9", new int[] { 1, 3, 1, 3, 1 });
		}

		/**
		 * No public access.
		 */
		protected ModuleTemplate() {}

	}

	/**
	 * Indicates whether the given character is valid for this barcode or not.
	 * This basically just checks to see whether the key is in the list of
	 * encoded characters.
	 * 
	 * @param key
	 *            The key to check for validity
	 * @return True if the key is valid, false otherwise
	 */
	static boolean isValid(String key) {
		return ModuleTemplate.SET.containsKey(key);
	}

	private String label;

	/**
	 * Constructs a new Standard 2 of 5 barcode with the specified data. No
	 * check digit will be added
	 * 
	 * @param data
	 *            The data to encode
	 * @throws BarcodeException
	 *             If the data is invalid
	 */
	Std2of5Barcode(String data) throws BarcodeException {
		this(data, false, 50);
	}

	/**
	 * Constructs a new Standard 2 of 5 barcode with thte specified data.
	 * 
	 * @param data
	 *            The data to encode
	 * @param checkDigit
	 *            if true then a check digit is automaticaly appened to data
	 * @throws BarcodeException
	 *             If the data is invalid
	 */
	Std2of5Barcode(String data, boolean checkDigit, int height) throws BarcodeException {
		super(checkDigit ? data + Modulo10.getMod10CheckDigit(data, 3) : data, height);
		this.label = data;

		validateData();
	}

	/**
	 * Calculates the check sum digit for the barcode.
	 * 
	 * @return Null - Standard 2 of 5 has no builtin checksum
	 */
	protected Module calculateChecksum() {
		return null; // No checksum - return null
	}

	public void cleanUp() {
	// TODO Auto-generated method stub

	}

	/**
	 * Encodes the data of the barcode into bars.
	 * 
	 * @return The encoded bar data
	 */
	protected Module[] encodeData() {
		Vector modules = new Vector();

		for (int i = 0; i < data.length(); i++) {
			Module module = ModuleTemplate.getModule(String.valueOf(data.charAt(i)));

			modules.addElement(module);
		}
		Module[] ret = new Module[modules.size()];
		modules.copyInto(ret);
		return ret;
	}

	/**
	 * Returns the text label to be displayed underneath the barcode.
	 * 
	 * @return The barcode label
	 */
	public String getLabel() {
		return label;
	}

	/**
	 * Returns the post-amble for the barcode.
	 * 
	 * @return A BlankModule
	 */
	protected Module getPostAmble() {
		return ModuleTemplate.END_CHAR;
	}

	/**
	 * Returns the pre-amble for the barcode.
	 * 
	 * @return A BlankModule
	 */
	protected Module getPreAmble() {
		return ModuleTemplate.START_CHAR;
	}

	protected void validateData() throws BarcodeException {
		int index = 0;
		StringBuffer buf = new StringBuffer();
		StringCharacterIterator iter = new StringCharacterIterator(data);
		for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
			if (!CharUtil.isWhitespace(c)) {
				if (!isValid(String.valueOf(c))) {
					throw new BarcodeException(c + " is not a valid character for Standard 2 of 5 encoding");
				}
				buf.append(c);
			}
			index += 1;
		}
		data = buf.toString();
	}
}
